home *** CD-ROM | disk | FTP | other *** search
/ EuroCD 3 / EuroCD 3.iso / Programming / Python-1.4 / Lib / cgi.py < prev    next >
Text File  |  1998-06-24  |  17KB  |  694 lines

  1. #!/usr/local/bin/python
  2.  
  3. # " <== Emacs font-lock de-bogo-kludgificocity
  4.  
  5. __version__ = "2.0"
  6.  
  7.  
  8. # Imports
  9. # =======
  10.  
  11. import string
  12. import sys
  13. import os
  14.  
  15.  
  16. # Logging support
  17. # ===============
  18.  
  19. logfile = ""        # Filename to log to, if not empty
  20. logfp = None        # File object to log to, if not None
  21.  
  22. def initlog(*allargs):
  23.     global logfp, log
  24.     if logfile and not logfp:
  25.     try:
  26.         logfp = open(logfile, "a")
  27.     except IOError:
  28.         pass
  29.     if not logfp:
  30.     log = nolog
  31.     else:
  32.     log = dolog
  33.     apply(log, allargs)
  34.  
  35. def dolog(fmt, *args):
  36.     logfp.write(fmt%args + "\n")
  37.  
  38. def nolog(*allargs):
  39.     pass
  40.  
  41. log = initlog        # The current logging function
  42.  
  43.  
  44. # Parsing functions
  45. # =================
  46.  
  47. def parse(fp=None, environ=os.environ, keep_blank_values=None):
  48.     if not fp:
  49.     fp = sys.stdin
  50.     if not environ.has_key('REQUEST_METHOD'):
  51.     environ['REQUEST_METHOD'] = 'GET'    # For testing stand-alone
  52.     if environ['REQUEST_METHOD'] == 'POST':
  53.     ctype, pdict = parse_header(environ['CONTENT_TYPE'])
  54.     if ctype == 'multipart/form-data':
  55.         return parse_multipart(fp, pdict)
  56.     elif ctype == 'application/x-www-form-urlencoded':
  57.         clength = string.atoi(environ['CONTENT_LENGTH'])
  58.         qs = fp.read(clength)
  59.     else:
  60.         qs = ''            # Unknown content-type
  61.     if environ.has_key('QUERY_STRING'): 
  62.         if qs: qs = qs + '&'
  63.         qs = qs + environ['QUERY_STRING']
  64.     elif sys.argv[1:]: 
  65.         if qs: qs = qs + '&'
  66.         qs = qs + sys.argv[1]
  67.     environ['QUERY_STRING'] = qs    # XXX Shouldn't, really
  68.     elif environ.has_key('QUERY_STRING'):
  69.     qs = environ['QUERY_STRING']
  70.     else:
  71.     if sys.argv[1:]:
  72.         qs = sys.argv[1]
  73.     else:
  74.         qs = ""
  75.     environ['QUERY_STRING'] = qs    # XXX Shouldn't, really
  76.     return parse_qs(qs, keep_blank_values)
  77.  
  78.  
  79. def parse_qs(qs, keep_blank_values=None):
  80.     import urllib, regsub
  81.     name_value_pairs = string.splitfields(qs, '&')
  82.     dict = {}
  83.     for name_value in name_value_pairs:
  84.     nv = string.splitfields(name_value, '=')
  85.     if len(nv) != 2:
  86.         continue
  87.     name = nv[0]
  88.     value = urllib.unquote(regsub.gsub('+', ' ', nv[1]))
  89.         if len(value) or keep_blank_values:
  90.         if dict.has_key (name):
  91.         dict[name].append(value)
  92.         else:
  93.         dict[name] = [value]
  94.     return dict
  95.  
  96.  
  97. def parse_multipart(fp, pdict):
  98.     import mimetools
  99.     if pdict.has_key('boundary'):
  100.     boundary = pdict['boundary']
  101.     else:
  102.     boundary = ""
  103.     nextpart = "--" + boundary
  104.     lastpart = "--" + boundary + "--"
  105.     partdict = {}
  106.     terminator = ""
  107.  
  108.     while terminator != lastpart:
  109.     bytes = -1
  110.     data = None
  111.     if terminator:
  112.         # At start of next part.  Read headers first.
  113.         headers = mimetools.Message(fp)
  114.         clength = headers.getheader('content-length')
  115.         if clength:
  116.         try:
  117.             bytes = string.atoi(clength)
  118.         except string.atoi_error:
  119.             pass
  120.         if bytes > 0:
  121.         data = fp.read(bytes)
  122.         else:
  123.         data = ""
  124.     # Read lines until end of part.
  125.     lines = []
  126.     while 1:
  127.         line = fp.readline()
  128.         if not line:
  129.         terminator = lastpart # End outer loop
  130.         break
  131.         if line[:2] == "--":
  132.         terminator = string.strip(line)
  133.         if terminator in (nextpart, lastpart):
  134.             break
  135.         lines.append(line)
  136.     # Done with part.
  137.     if data is None:
  138.         continue
  139.     if bytes < 0:
  140.         if lines:
  141.         # Strip final line terminator
  142.         line = lines[-1]
  143.         if line[-2:] == "\r\n":
  144.             line = line[:-2]
  145.         elif line[-1:] == "\n":
  146.             line = line[:-1]
  147.         lines[-1] = line
  148.         data = string.joinfields(lines, "")
  149.     line = headers['content-disposition']
  150.     if not line:
  151.         continue
  152.     key, params = parse_header(line)
  153.     if key != 'form-data':
  154.         continue
  155.     if params.has_key('name'):
  156.         name = params['name']
  157.     else:
  158.         continue
  159.     if partdict.has_key(name):
  160.         partdict[name].append(data)
  161.     else:
  162.         partdict[name] = [data]
  163.  
  164.     return partdict
  165.  
  166.  
  167. def parse_header(line):
  168.     plist = map(string.strip, string.splitfields(line, ';'))
  169.     key = string.lower(plist[0])
  170.     del plist[0]
  171.     pdict = {}
  172.     for p in plist:
  173.     i = string.find(p, '=')
  174.     if i >= 0:
  175.         name = string.lower(string.strip(p[:i]))
  176.         value = string.strip(p[i+1:])
  177.         if len(value) >= 2 and value[0] == value[-1] == '"':
  178.         value = value[1:-1]
  179.         pdict[name] = value
  180.     return key, pdict
  181.  
  182.  
  183. # Classes for field storage
  184. # =========================
  185.  
  186. class MiniFieldStorage:
  187.  
  188.     """Like FieldStorage, for use when no file uploads are possible."""
  189.  
  190.     # Dummy attributes
  191.     filename = None
  192.     list = None
  193.     type = None
  194.     file = None
  195.     type_options = {}
  196.     disposition = None
  197.     disposition_options = {}
  198.     headers = {}
  199.  
  200.     def __init__(self, name, value):
  201.     """Constructor from field name and value."""
  202.     from StringIO import StringIO
  203.     self.name = name
  204.     self.value = value
  205.         # self.file = StringIO(value)
  206.  
  207.     def __repr__(self):
  208.     """Return printable representation."""
  209.     return "MiniFieldStorage(%s, %s)" % (`self.name`, `self.value`)
  210.  
  211.  
  212. class FieldStorage:
  213.     def __init__(self, fp=None, headers=None, outerboundary="",
  214.          environ=os.environ, keep_blank_values=None):
  215.     method = None
  216.     self.keep_blank_values = keep_blank_values
  217.     if environ.has_key('REQUEST_METHOD'):
  218.         method = string.upper(environ['REQUEST_METHOD'])
  219.     if not fp and method == 'GET':
  220.         qs = None
  221.         if environ.has_key('QUERY_STRING'):
  222.         qs = environ['QUERY_STRING']
  223.         from StringIO import StringIO
  224.         fp = StringIO(qs or "")
  225.         if headers is None:
  226.         headers = {'content-type':
  227.                "application/x-www-form-urlencoded"}
  228.     if headers is None:
  229.         headers = {}
  230.         if environ.has_key('CONTENT_TYPE'):
  231.         headers['content-type'] = environ['CONTENT_TYPE']
  232.         if environ.has_key('CONTENT_LENGTH'):
  233.         headers['content-length'] = environ['CONTENT_LENGTH']
  234.     self.fp = fp or sys.stdin
  235.     self.headers = headers
  236.     self.outerboundary = outerboundary
  237.  
  238.     # Process content-disposition header
  239.     cdisp, pdict = "", {}
  240.     if self.headers.has_key('content-disposition'):
  241.         cdisp, pdict = parse_header(self.headers['content-disposition'])
  242.     self.disposition = cdisp
  243.     self.disposition_options = pdict
  244.     self.name = None
  245.     if pdict.has_key('name'):
  246.         self.name = pdict['name']
  247.     self.filename = None
  248.     if pdict.has_key('filename'):
  249.         self.filename = pdict['filename']
  250.  
  251.     # Process content-type header
  252.     ctype, pdict = "text/plain", {}
  253.     if self.headers.has_key('content-type'):
  254.         ctype, pdict = parse_header(self.headers['content-type'])
  255.     self.type = ctype
  256.     self.type_options = pdict
  257.     self.innerboundary = ""
  258.     if pdict.has_key('boundary'):
  259.         self.innerboundary = pdict['boundary']
  260.     clen = -1
  261.     if self.headers.has_key('content-length'):
  262.         try:
  263.         clen = string.atoi(self.headers['content-length'])
  264.         except:
  265.         pass
  266.     self.length = clen
  267.  
  268.     self.list = self.file = None
  269.     self.done = 0
  270.     self.lines = []
  271.     if ctype == 'application/x-www-form-urlencoded':
  272.         self.read_urlencoded()
  273.     elif ctype[:10] == 'multipart/':
  274.         self.read_multi()
  275.     else:
  276.         self.read_single()
  277.  
  278.     def __repr__(self):
  279.     """Return a printable representation."""
  280.     return "FieldStorage(%s, %s, %s)" % (
  281.         `self.name`, `self.filename`, `self.value`)
  282.  
  283.     def __getattr__(self, name):
  284.     if name != 'value':
  285.         raise AttributeError, name
  286.     if self.file:
  287.         self.file.seek(0)
  288.         value = self.file.read()
  289.         self.file.seek(0)
  290.     elif self.list is not None:
  291.         value = self.list
  292.     else:
  293.         value = None
  294.     return value
  295.  
  296.     def __getitem__(self, key):
  297.     """Dictionary style indexing."""
  298.     if self.list is None:
  299.         raise TypeError, "not indexable"
  300.     found = []
  301.     for item in self.list:
  302.         if item.name == key: found.append(item)
  303.     if not found:
  304.         raise KeyError, key
  305.     if len(found) == 1:
  306.         return found[0]
  307.     else:
  308.         return found
  309.  
  310.     def keys(self):
  311.     """Dictionary style keys() method."""
  312.     if self.list is None:
  313.         raise TypeError, "not indexable"
  314.     keys = []
  315.     for item in self.list:
  316.         if item.name not in keys: keys.append(item.name)
  317.     return keys
  318.  
  319.     def has_key(self, key):
  320.     """Dictionary style has_key() method."""
  321.     if self.list is None:
  322.         raise TypeError, "not indexable"
  323.     for item in self.list:
  324.         if item.name == key: return 1
  325.     return 0
  326.  
  327.     def read_urlencoded(self):
  328.     """Internal: read data in query string format."""
  329.     qs = self.fp.read(self.length)
  330.         dict = parse_qs(qs, self.keep_blank_values)
  331.     self.list = []
  332.     for key, valuelist in dict.items():
  333.         for value in valuelist:
  334.         self.list.append(MiniFieldStorage(key, value))
  335.     self.skip_lines()
  336.  
  337.     def read_multi(self):
  338.     """Internal: read a part that is itself multipart."""
  339.     import rfc822
  340.     self.list = []
  341.     part = self.__class__(self.fp, {}, self.innerboundary)
  342.     # Throw first part away
  343.     while not part.done:
  344.         headers = rfc822.Message(self.fp)
  345.         part = self.__class__(self.fp, headers, self.innerboundary)
  346.         self.list.append(part)
  347.     self.skip_lines()
  348.  
  349.     def read_single(self):
  350.     """Internal: read an atomic part."""
  351.     if self.length >= 0:
  352.         self.read_binary()
  353.         self.skip_lines()
  354.     else:
  355.         self.read_lines()
  356.     self.file.seek(0)
  357.  
  358.     bufsize = 8*1024        # I/O buffering size for copy to file
  359.  
  360.     def read_binary(self):
  361.     """Internal: read binary data."""
  362.     self.file = self.make_file('b')
  363.     todo = self.length
  364.     if todo >= 0:
  365.         while todo > 0:
  366.         data = self.fp.read(min(todo, self.bufsize))
  367.         if not data:
  368.             self.done = -1
  369.             break
  370.         self.file.write(data)
  371.         todo = todo - len(data)
  372.  
  373.     def read_lines(self):
  374.     """Internal: read lines until EOF or outerboundary."""
  375.     self.file = self.make_file('')
  376.     if self.outerboundary:
  377.         self.read_lines_to_outerboundary()
  378.     else:
  379.         self.read_lines_to_eof()
  380.  
  381.     def read_lines_to_eof(self):
  382.     """Internal: read lines until EOF."""
  383.     while 1:
  384.         line = self.fp.readline()
  385.         if not line:
  386.         self.done = -1
  387.         break
  388.         self.lines.append(line)
  389.         self.file.write(line)
  390.  
  391.     def read_lines_to_outerboundary(self):
  392.     """Internal: read lines until outerboundary."""
  393.     next = "--" + self.outerboundary
  394.     last = next + "--"
  395.     delim = ""
  396.     while 1:
  397.         line = self.fp.readline()
  398.         if not line:
  399.         self.done = -1
  400.         break
  401.         self.lines.append(line)
  402.         if line[:2] == "--":
  403.         strippedline = string.strip(line)
  404.         if strippedline == next:
  405.             break
  406.         if strippedline == last:
  407.             self.done = 1
  408.             break
  409.         odelim = delim
  410.         if line[-2:] == "\r\n":
  411.         delim = "\r\n"
  412.         line = line[:-2]
  413.         elif line[-1] == "\n":
  414.         delim = "\n"
  415.         line = line[:-1]
  416.         else:
  417.         delim = ""
  418.         self.file.write(odelim + line)
  419.  
  420.     def skip_lines(self):
  421.     """Internal: skip lines until outer boundary if defined."""
  422.     if not self.outerboundary or self.done:
  423.         return
  424.     next = "--" + self.outerboundary
  425.     last = next + "--"
  426.     while 1:
  427.         line = self.fp.readline()
  428.         if not line:
  429.         self.done = -1
  430.         break
  431.         self.lines.append(line)
  432.         if line[:2] == "--":
  433.         strippedline = string.strip(line)
  434.         if strippedline == next:
  435.             break
  436.         if strippedline == last:
  437.             self.done = 1
  438.             break
  439.  
  440.     def make_file(self, binary):
  441.     import tempfile
  442.     tfn = tempfile.mktemp()
  443.     f = open(tfn, "w%s+" % binary)
  444.     os.unlink(tfn)
  445.     return f
  446.  
  447.  
  448. # Backwards Compatibility Classes
  449. # ===============================
  450.  
  451. class FormContentDict:
  452.     def __init__(self, environ=os.environ):
  453.         self.dict = parse(environ=environ)
  454.     self.query_string = environ['QUERY_STRING']
  455.     def __getitem__(self,key):
  456.     return self.dict[key]
  457.     def keys(self):
  458.     return self.dict.keys()
  459.     def has_key(self, key):
  460.     return self.dict.has_key(key)
  461.     def values(self):
  462.     return self.dict.values()
  463.     def items(self):
  464.     return self.dict.items() 
  465.     def __len__( self ):
  466.     return len(self.dict)
  467.  
  468.  
  469. class SvFormContentDict(FormContentDict):
  470.     def __getitem__(self, key):
  471.     if len(self.dict[key]) > 1: 
  472.         raise IndexError, 'expecting a single value' 
  473.     return self.dict[key][0]
  474.     def getlist(self, key):
  475.     return self.dict[key]
  476.     def values(self):
  477.     lis = []
  478.     for each in self.dict.values(): 
  479.         if len( each ) == 1 : 
  480.         lis.append(each[0])
  481.         else: lis.append(each)
  482.     return lis
  483.     def items(self):
  484.     lis = []
  485.     for key,value in self.dict.items():
  486.         if len(value) == 1 :
  487.         lis.append((key, value[0]))
  488.         else:    lis.append((key, value))
  489.     return lis
  490.  
  491.  
  492. class InterpFormContentDict(SvFormContentDict):
  493.     """This class is present for backwards compatibility only.""" 
  494.     def __getitem__( self, key ):
  495.     v = SvFormContentDict.__getitem__( self, key )
  496.     if v[0] in string.digits+'+-.' : 
  497.         try:  return  string.atoi( v ) 
  498.         except ValueError:
  499.         try:    return string.atof( v )
  500.         except ValueError: pass
  501.     return string.strip(v)
  502.     def values( self ):
  503.     lis = [] 
  504.     for key in self.keys():
  505.         try:
  506.         lis.append( self[key] )
  507.         except IndexError:
  508.         lis.append( self.dict[key] )
  509.     return lis
  510.     def items( self ):
  511.     lis = [] 
  512.     for key in self.keys():
  513.         try:
  514.         lis.append( (key, self[key]) )
  515.         except IndexError:
  516.         lis.append( (key, self.dict[key]) )
  517.     return lis
  518.  
  519.  
  520. class FormContent(FormContentDict):
  521.     """This class is present for backwards compatibility only.""" 
  522.     def values(self, key):
  523.     if self.dict.has_key(key) :return self.dict[key]
  524.     else: return None
  525.     def indexed_value(self, key, location):
  526.     if self.dict.has_key(key):
  527.         if len (self.dict[key]) > location:
  528.         return self.dict[key][location]
  529.         else: return None
  530.     else: return None
  531.     def value(self, key):
  532.     if self.dict.has_key(key): return self.dict[key][0]
  533.     else: return None
  534.     def length(self, key):
  535.     return len(self.dict[key])
  536.     def stripped(self, key):
  537.     if self.dict.has_key(key): return string.strip(self.dict[key][0])
  538.     else: return None
  539.     def pars(self):
  540.     return self.dict
  541.  
  542.  
  543. # Test/debug code
  544. # ===============
  545.  
  546. def test(environ=os.environ):
  547.     """Robust test CGI script, usable as main program.
  548.  
  549.     Write minimal HTTP headers and dump all information provided to
  550.     the script in HTML form.
  551.  
  552.     """
  553.     import traceback
  554.     print "Content-type: text/html"
  555.     print
  556.     sys.stderr = sys.stdout
  557.     try:
  558.     form = FieldStorage()    # Replace with other classes to test those
  559.     print_form(form)
  560.         print_environ(environ)
  561.     print_directory()
  562.     print_arguments()
  563.     print_environ_usage()
  564.     def f():
  565.         exec "testing print_exception() -- <I>italics?</I>"
  566.     def g(f=f):
  567.         f()
  568.     print "<H3>What follows is a test, not an actual exception:</H3>"
  569.     g()
  570.     except:
  571.     print_exception()
  572.  
  573. def print_exception(type=None, value=None, tb=None, limit=None):
  574.     if type is None:
  575.     type, value, tb = sys.exc_type, sys.exc_value, sys.exc_traceback
  576.     import traceback
  577.     print
  578.     print "<H3>Traceback (innermost last):</H3>"
  579.     list = traceback.format_tb(tb, limit) + \
  580.        traceback.format_exception_only(type, value)
  581.     print "<PRE>%s<B>%s</B></PRE>" % (
  582.     escape(string.join(list[:-1], "")),
  583.     escape(list[-1]),
  584.     )
  585.  
  586. def print_environ(environ=os.environ):
  587.     """Dump the shell environment as HTML."""
  588.     keys = environ.keys()
  589.     keys.sort()
  590.     print
  591.     print "<H3>Shell Environment:</H3>"
  592.     print "<DL>"
  593.     for key in keys:
  594.     print "<DT>", escape(key), "<DD>", escape(environ[key])
  595.     print "</DL>" 
  596.     print
  597.  
  598. def print_form(form):
  599.     """Dump the contents of a form as HTML."""
  600.     keys = form.keys()
  601.     keys.sort()
  602.     print
  603.     print "<H3>Form Contents:</H3>"
  604.     print "<DL>"
  605.     for key in keys:
  606.     print "<DT>" + escape(key) + ":",
  607.     value = form[key]
  608.     print "<i>" + escape(`type(value)`) + "</i>"
  609.     print "<DD>" + escape(`value`)
  610.     print "</DL>"
  611.     print
  612.  
  613. def print_directory():
  614.     """Dump the current directory as HTML."""
  615.     print
  616.     print "<H3>Current Working Directory:</H3>"
  617.     try:
  618.     pwd = os.getcwd()
  619.     except os.error, msg:
  620.     print "os.error:", escape(str(msg))
  621.     else:
  622.     print escape(pwd)
  623.     print
  624.  
  625. def print_arguments():
  626.     print
  627.     print "<H3>Command Line Arguments:</H3>"
  628.     print
  629.     print sys.argv
  630.     print
  631.  
  632. def print_environ_usage():
  633.     """Dump a list of environment variables used by CGI as HTML."""
  634.     print """
  635. <H3>These environment variables could have been set:</H3>
  636. <UL>
  637. <LI>AUTH_TYPE
  638. <LI>CONTENT_LENGTH
  639. <LI>CONTENT_TYPE
  640. <LI>DATE_GMT
  641. <LI>DATE_LOCAL
  642. <LI>DOCUMENT_NAME
  643. <LI>DOCUMENT_ROOT
  644. <LI>DOCUMENT_URI
  645. <LI>GATEWAY_INTERFACE
  646. <LI>LAST_MODIFIED
  647. <LI>PATH
  648. <LI>PATH_INFO
  649. <LI>PATH_TRANSLATED
  650. <LI>QUERY_STRING
  651. <LI>REMOTE_ADDR
  652. <LI>REMOTE_HOST
  653. <LI>REMOTE_IDENT
  654. <LI>REMOTE_USER
  655. <LI>REQUEST_METHOD
  656. <LI>SCRIPT_NAME
  657. <LI>SERVER_NAME
  658. <LI>SERVER_PORT
  659. <LI>SERVER_PROTOCOL
  660. <LI>SERVER_ROOT
  661. <LI>SERVER_SOFTWARE
  662. </UL>
  663. In addition, HTTP headers sent by the server may be passed in the
  664. environment as well.  Here are some common variable names:
  665. <UL>
  666. <LI>HTTP_ACCEPT
  667. <LI>HTTP_CONNECTION
  668. <LI>HTTP_HOST
  669. <LI>HTTP_PRAGMA
  670. <LI>HTTP_REFERER
  671. <LI>HTTP_USER_AGENT
  672. </UL>
  673. """
  674.  
  675.  
  676. # Utilities
  677. # =========
  678.  
  679. def escape(s):
  680.     """Replace special characters '&', '<' and '>' by SGML entities."""
  681.     import regsub
  682.     s = regsub.gsub("&", "&", s)    # Must be done first!
  683.     s = regsub.gsub("<", "<", s)
  684.     s = regsub.gsub(">", ">", s)
  685.     return s
  686.  
  687.  
  688. # Invoke mainline
  689. # ===============
  690.  
  691. # Call test() when this file is run as a script (not imported as a module)
  692. if __name__ == '__main__': 
  693.     test()
  694.